package com.hup.minicontroller.common.netty.handler;

import android.util.Log;

import com.hup.minicontroller.common.netty.NettyConstant.DataType;
import com.hup.minicontroller.common.netty.model.LoginRequestPacket;
import com.hup.minicontroller.common.netty.model.LoginResponsePacket;

import java.lang.reflect.Modifier;
import java.net.SocketAddress;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.AttributeKey;

/**
 * 服务端的登陆认证处理器
 *
 * @author hugan
 * @date 2021/4/25
 */
@Sharable
public abstract class LoginHandler<T extends LoginRequestPacket, R extends LoginResponsePacket> extends ChannelInboundHandlerAdapter {
    private static final String TAG = LoginHandler.class.getSimpleName();
    private static final AttributeKey<LoginRequestPacket> LOGIN_REQUEST_DATA = AttributeKey.newInstance("LOGIN_REQUEST_DATA");


    /**
     * 添加了登陆认证处理器后,逻辑过程:
     * .客户端连接成功channelActive,客户端发送第一条消息必须是 {@link LoginRequestPacket}
     * .服务端被连接成功->initChannel->本来被添加到pipeline,触发所有处理器的channelActive
     * .服务端socket收到消息,到达本方法->进行认证
     * ->认证成功或失败后,本处理器实例都会从pipeline中被移除
     */
    @Override
    @SuppressWarnings("unchecked")
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        Log.d(TAG, "channelRead: " + msg);
        T loginReq;
        try {
            loginReq = (T) msg;//如果是其他程序,msg可能是错误数据或其他对象导致强转异常
        } catch (Exception e) {
            responseErrorAndClose(ctx, "认证请求体异常");
            return;
        }
        try {
            doLoginValid(ctx.channel().remoteAddress(), loginReq);
            onLoginValidSuccess(ctx.channel(), loginReq);
            Log.d(TAG, "channelRead: 认证成功");
            ctx.pipeline().remove(this); //认证成功,移除本处理器
            R response = getLoginSuccessResponse(ctx.channel(), loginReq);//回复客户端
            ctx.channel().writeAndFlush(response);
            super.channelRead(ctx, msg);//传递给后面的处理器,代表认证成功,实现"认证成功后才通知回调"
        } catch (LoginException e) {
            responseErrorAndClose(ctx, e.getMessage());
        } catch (Exception e) {
            responseErrorAndClose(ctx, "服务器异常");
            //不用外抛异常了,因为是程序错误,且channel都关闭了,直接打印日志
            Log.e(TAG, "channelRead: 服务器异常", e);
        }
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        //log.info("");//channelRead被调用一次后,都会到达此处
    }

    private void responseErrorAndClose(ChannelHandlerContext ctx, String err) {
        Log.w(TAG, "responseErrorAndClose: 认证失败:" + ctx.channel().remoteAddress() + ", " + err);
        ctx.channel().writeAndFlush(LoginResponsePacket.failure(err));
        ctx.channel().close();
    }

    /**
     * 获取登陆请求的Class,用于注册解码 {@link LoginRequestPacket}
     */
    protected abstract Class<T> getLoginRequestType();

    /**
     * 校验登陆认证请求;
     * .认证成功时无返回
     * .认证错误时抛出异常 {@link LoginException}
     */
    protected abstract void doLoginValid(SocketAddress address, T data) throws LoginException;

    /**
     * 获取认证成功回复;用于认证成功后向客户端发送回复
     */
    protected abstract R getLoginSuccessResponse(Channel channel, T loginReq);

    /**
     * 认证成功后保存attr
     */
    private void onLoginValidSuccess(Channel channel, T data) {
        channel.attr(LOGIN_REQUEST_DATA).set(data);
    }

    @SuppressWarnings("unchecked")
    public T getLoginRequestData(Channel channel) {
        return (T) channel.attr(LOGIN_REQUEST_DATA).get();
    }

    /**
     * @return 该连接是否认证成功
     */
    public static boolean isLoginValidSuccess(Channel channel) {
        LoginRequestPacket attr = channel.attr(LOGIN_REQUEST_DATA).get();
        return attr != null;
    }

    /**
     * 处理 {@link LoginRequestPacket}的解码功能;
     * 在绑定 {@link LoginHandler}时调用
     */
    public static void handleLoginRequestCodec(LoginHandler<?, ?> loginHandler) {
        if (loginHandler != null) {
            Class<? extends LoginRequestPacket> requestType = loginHandler.getLoginRequestType();
            int modifiers = requestType.getModifiers();
            if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)) {
                throw new IllegalArgumentException("传入的[LoginRequestPacket]必须是实现类,不能为抽象类或接口:" + requestType);
            }
            DataCodecHandler.INSTANCE.registerType(requestType);
        } else {
            DataCodecHandler.INSTANCE.unRegisterType(DataType.LOGIN_REQUEST);
        }
    }

    public static class LoginException extends RuntimeException {
        public LoginException(String msg) {
            super(msg);
        }
    }

}
